"""
Folder on file system.
"""

import os
import sys
import fnmatch
import tempfile
import warnings
import fimero.core.disk as disk
import fimero.core.disk.NodeKit as NodeKit
import fimero.core.disk.PathKit as PathKit
import fimero.core.disk.FileKit as FileKit

################################################################################
# Classes
################################################################################

class TreeWalkWarning(Warning):
    """Warning generated when walking file system trees."""
    pass

class TFolder(NodeKit.TNode):
    """TFolder represents file system folders."""
    # default create mode
    CREATE_MODE = 0755
    
    def __init__(self, path):
        NodeKit.TNode.__init__(self, path)
        
    def list(self, pattern=None):
        """List contents of folder. Returns path objects."""
        names = os.listdir(repr(self.get_path()))
        if pattern is not None:
            names = fnmatch.filter(names, pattern)
        
        return [PathKit.make_path(self.path, name) for name in names]
    
    def list_folders(self, pattern=None):
        """list of paths to subfolders."""
        return [p for p in self.list(pattern) if p.is_folder()]
    
    def walk(self, pattern=None, errors=disk.STRICT):
        """folder.walk() iterates over files and folders, recursively.
        
        The iterator yields path objects naming each child item of
        this directory and its descendants.  This requires that
        D.isdir().

        This performs a depth-first traversal of the directory tree.
        Each directory is returned just before all its children.

        The errors= keyword argument controls behavior when an
        error occurs.  The default is 'strict', which causes an
        exception.  The other allowed values are 'warn', which
        reports the error via warnings.warn(), and 'ignore'.
        """
        is_valid_errors(errors)
        
        try:
            child_list = self.list()
        except Exception, exception:
            process_list_error(self, errors, exception)
            
        for child in child_list:
            if pattern is None or child.fnmatch(pattern):
                yield child
            try:
                is_folder = child.is_folder()
            except Exception, exception:
                process_access_error(self, errors, exception)
                
            if is_folder:
                child_folder = make(child)
                for item in child_folder.walk(pattern, errors):
                    yield item

    def walk_folders(self, pattern=None, errors=disk.STRICT):
        """walk_folders iterates over subdirectories recursively."""
        is_valid_errors(errors)
        
        try:
            folders = self.list_folders()
        except Exception, exception:
            process_list_error(self, errors, exception)
            
        for child in folders:
            if pattern is None or child.fnmatch(pattern):
                yield child
            child_folder = make(child)
            for subfolder in child_folder.walk_folders(pattern, errors):
                yield subfolder
            
    def make_file(self, path):
        """creates a new, empty file. returns a path instance."""
        path = PathKit.make(path)
        joined_path = self.get_path().join(path)
        new_file = FileKit.make(joined_path)
        new_file.touch()
        return joined_path
    
    def make_folder(self, path):
        """creates a new, empty folder. returns a path instance."""
        path = PathKit.make(path)
        joined_path = self.get_path().join(path)
        new_folder = make(joined_path)
        new_folder.touch()
        return joined_path
    
    def delete(self):
        """delete this folder and all content."""
        self.get_path().delete()
        
    def touch(self):
        """create folder if it does not exist."""        
        if not self.exists():
            os.makedirs(repr(self), mode=0755)
        else:
            os.utime(repr(self), None)
        
################################################################################
# Module methods
################################################################################

def is_valid_errors(errors):
    """Check if the specified errors are supported."""
    if errors not in disk.ERRORS:
        raise ValueError('invalid errors parameter: ' + errors)

def process_list_error(folder, errors, exception):
    """process a listing error."""
    if errors == disk.IGNORE:
        return
    elif errors == disk.WARN:
        warnings.warn("Unable to list directory '%s': %s" % 
                      (repr(folder.get_path()), sys.exc_info()[1]), 
                      TreeWalkWarning)
    else:
        raise exception
    
def process_access_error(folder, errors, exception):
    """process access error."""
    if errors == disk.IGNORE:
        return
    elif errors == disk.WARN:
        warnings.warn("Unable to access '%s' : %s" %
                      (repr(folder.get_path()), sys.exc_info()[1]),
                      TreeWalkWarning)
    else:
        raise exception
    
def make(path):
    """makes a new folder instance."""
    path = PathKit.make(path)
    folder = TFolder(path)
    return folder

def make_temp():
    """makes a temporary folder."""
    temp_path = tempfile.mkdtemp()
    return make(temp_path)